#ifndef AER_CAMERA_HPP_
#define AER_CAMERA_HPP_

#include "aerView3D.hpp"
#include "aerView.hpp"
#include "aerFrustum.hpp"


namespace aer {
  
/// + ~ + ~ ~ + ~ ~ + ~ ~ +
///
/// Describes a camera.
///
/// + ~ + ~ ~ + ~ ~ + ~ ~ +
class Camera : public View3D
{
  protected:
    View    m_view;
    Frustum m_frustum;

    Matrix4x4 m_viewProjMatrix;
    bool      m_bRebuild;
    
    
  public:
    Camera(const View &view, const Frustum &frustum)
          : m_view(view),
            m_frustum(frustum),
            m_bRebuild(true)
    {}
    /*
    const Camera& operator =(Camera &camera)
    {
      build();
      return *this;
    }
    */
 
    /// ===========
    /// + Setters +
    /// ===========
    void setView(const View& view) 
    {
      m_view = view;
      m_bRebuild = true;
    }
    
    void setFrustum(const Frustum& frustum)
    {
      m_frustum = frustum;
      m_bRebuild = true;
    }
    
    void setPosition(const Vector3& position)   { m_view.setPosition(position);   }
    void setDirection(const Vector3& direction) { m_view.setDirection(direction); }
    void setTarget(const Vector3& target)       { m_view.setTarget(target);    }
 
 
    /// ===========
    /// + Getters +
    /// ===========
    const View&    getView()      const { return m_view; }
    const Frustum& getFrustum()   const { return m_frustum; }
    
    const Vector3& getPosition()  const { return m_view.getPosition(); }
    const Vector3& getDirection() const { return m_view.getDirection(); }
    const Vector3& getUp()        const { return m_view.getUp(); }
    
    
    // TODO : problems, rebuilding the matrix dynamically disallow the use
    // of constant Getters (matrices MUST be up to date).
    
    const Matrix4x4& getViewMatrix()
    {
      m_bRebuild = m_bRebuild || m_view.isDirty();
      return m_view.getViewMatrix();
    }
    
    const Matrix4x4& getProjectionMatrix()
    {
      m_bRebuild = m_bRebuild || m_frustum.isDirty();
      return m_frustum.getProjectionMatrix();
    }
    
    const Matrix4x4& getViewProjectionMatrix()
    {
      if (isDirty()) {
        build();
      }
      return m_viewProjMatrix;
    }
    
    const Matrix4x4& getViewProjectionMatrix() const //
    {
      AER_ASSERT( !isDirty() );
      return m_viewProjMatrix;
    }
    
    bool isDirty() const
    {
      return m_bRebuild || m_frustum.isDirty() || m_view.isDirty();
    }
    
    void build()
    {
      m_viewProjMatrix = m_frustum.getProjectionMatrix() * m_view.getViewMatrix();
      m_bRebuild = false;
    }
    
    /// Catch event and update the camera attributes    
    virtual void update() {}
};
  
}

#endif // AER_CAMERA_HPP_
